package git.soulbgm.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import git.soulbgm.common.redis.RedisTool;
import git.soulbgm.mapper.AlarmInfoMapper;
import git.soulbgm.pojo.*;
import git.soulbgm.pojo.vo.AlarmCountVo;
import git.soulbgm.service.AlarmPromptConfigService;
import git.soulbgm.service.AlarmRuleInfoService;
import git.soulbgm.utils.ClassUtil;
import org.springframework.stereotype.Service;
import git.soulbgm.service.AlarmInfoService;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static git.soulbgm.common.MonitorConstants.*;

/**
 * 告警信息表
 *
 * @author SoulBGM
 * @date 2020-11-18 10:03:05
 */
@Service
public class AlarmInfoServiceImpl extends ServiceImpl<AlarmInfoMapper, AlarmInfo> implements AlarmInfoService {

    private final AlarmRuleInfoService ruleInfoService;
    private final AlarmPromptConfigService alarmPromptConfigService;
    private final RedisTool redis;

    public AlarmInfoServiceImpl(AlarmRuleInfoService ruleInfoService, AlarmPromptConfigService alarmPromptConfigService, RedisTool redis) {
        this.ruleInfoService = ruleInfoService;
        this.alarmPromptConfigService = alarmPromptConfigService;
        this.redis = redis;
    }

    @Override
    public Map<Integer, Integer> getAlarmCountMap(SFunction<AlarmInfo, Object> function) {
        List<Map<String, Object>> maps = listMaps(new QueryWrapper<AlarmInfo>().select("level", "count(1) count").lambda()
                .eq(function, HANDLE_NO).groupBy(AlarmInfo::getLevel));
        return maps.stream().collect(Collectors.toMap(o -> toInt(o.get("level")), o -> toInt(o.get("count"))));
    }

    /**
     * 将Object转换int
     *
     * @param obj 对象
     * @return 整数值
     */
    private int toInt(Object obj) {
        return obj == null ? 0 : Integer.parseInt(obj.toString());
    }

    private String getFieldName(SFunction<AlarmInfo, Object> function) {
        try {
            return ClassUtil.getFieldName(ClassUtil.getImplMethodName(function));
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Map<Integer, Integer> getCacheAlarmCountMap(SFunction<AlarmInfo, Object> function) {
        String fieldName = getFieldName(function);
        Map<String, Integer> map = redis.hgetAll(REDIS_KEY_ALARM_GROUP_COUNT + fieldName);
        if (!map.isEmpty()) {
            Map<Integer, Integer> convertMap = new HashMap<>(map.size());
            map.forEach((k, v) -> convertMap.put(Integer.parseInt(k), v));
            return convertMap;
        }
        return resetCacheAlarmCountMap(function);
    }

    @Override
    public void saveCacheAlarmCountMap(Map<Integer, Integer> map, SFunction<AlarmInfo, Object> function) {
        Map<String, Integer> convertMap = new HashMap<>(map.size());
        map.forEach((k, v) -> convertMap.put(String.valueOf(k), v));
        String fieldName = getFieldName(function);
        String key = REDIS_KEY_ALARM_GROUP_COUNT + fieldName;
        redis.hsetAll(key, convertMap);
        redis.expire(key, ALARM_COUNT_CACHE_EXPIRE_TIME, TimeUnit.SECONDS);
    }

    @Override
    public Map<Integer, Integer> resetCacheAlarmCountMap(SFunction<AlarmInfo, Object> function) {
        Map<Integer, Integer> map = getAlarmCountMap(function);
        saveCacheAlarmCountMap(map, function);
        return map;
    }

    @Override
    public AlarmCountVo getAlarmCountVo() {
        Map<Integer, Integer> map = getCacheAlarmCountMap(AlarmInfo::getReadHandle);
        AlarmCountVo vo = new AlarmCountVo();
        vo.setGeneralCount(map.getOrDefault(ALARM_LEVEL_GENERAL, 0));
        vo.setPrimaryCount(map.getOrDefault(ALARM_LEVEL_PRIMARY, 0));
        vo.setSeriousCount(map.getOrDefault(ALARM_LEVEL_SERIOUS, 0));
        vo.setTwinkling(0);
        vo.setRingBell(0);
        Map<Integer, AlarmPromptConfig> promptConfigMap = alarmPromptConfigService.getAlarmPromptConfigMap();
        for (int level : ALARM_LEVEL_ALL) {
            add(map, vo, promptConfigMap, level);
        }
        vo.setTwinkling(vo.getTwinkling() >= 1 ? 1 : 0);
        vo.setRingBell(vo.getRingBell() >= 1 ? 1 : 0);
        return vo;
    }

    /**
     * 添加
     *
     * @param map             告警计数Map
     * @param vo              展示类
     * @param promptConfigMap 告警提示配置Map
     * @param level           等级
     */
    private void add(Map<Integer, Integer> map, AlarmCountVo vo, Map<Integer, AlarmPromptConfig> promptConfigMap, int level) {
        if (map.getOrDefault(level, 0) > 0) {
            AlarmPromptConfig config = promptConfigMap.get(level);
            vo.setTwinkling(vo.getTwinkling() + Optional.ofNullable(config.getTwinkling()).orElse(0));
            vo.setRingBell(vo.getRingBell() + Optional.ofNullable(config.getRingBell()).orElse(0));
        }
    }

    @Override
    public boolean batchUpdate(List<Long> ids, Integer handleType, String artificialMessage) {
        LambdaUpdateWrapper<AlarmInfo> wrapper = new LambdaUpdateWrapper<>();

        if (handleType == HANDLE_TYPE_ARTIFICIAL) {
            wrapper.eq(AlarmInfo::getMachineHandle, HANDLE_COMPLETE);
        }
        wrapper.in(AlarmInfo::getId, ids);

        AlarmInfo info = new AlarmInfo();
        if (handleType == HANDLE_TYPE_ARTIFICIAL) {
            info.setArtificialHandle(HANDLE_COMPLETE);
            info.setArtificialHandleTime(System.currentTimeMillis());
            info.setArtificialMessage(artificialMessage);
        } else {
            info.setMachineHandle(HANDLE_COMPLETE);
            info.setMachineHandleTime(System.currentTimeMillis());
        }
        return update(info, wrapper);
    }

    @Override
    public boolean insertAlarmInfo(ProduceAlarmInfo produceAlarmInfo, AlarmRuleInfo rule) {
        Integer continuousNumber = rule.getContinuousNumber();
        Integer machineParticipation = rule.getMachineParticipation();
        String alarmObject = produceAlarmInfo.getAlarmObject();
        String keySuffix = rule.getId() + ":" + alarmObject;
        String key = REDIS_KEY_ALARM_COUNT + keySuffix;
        Long count = redis.increment(key, 1);
        boolean flag = count.intValue() >= continuousNumber;

        if (flag) {
            // 次数达成 开始新增 新增时查看相同情况的告警是否已存在 如果存在就不用新增了
            // 如果该规则是需要机器参与的则代表不需要看人工是否处理了 反之则需要看人工是否处理了
            String alarmUntreatedKey = REDIS_KEY_ALARM_UNTREATED + keySuffix;
            boolean isNotExist = redis.setNx(alarmUntreatedKey, System.currentTimeMillis() + "");

            boolean isMachineParticipation = machineParticipation == MACHINE_PARTICIPATION;

            if (isNotExist) {
                // 说明告警不存在需要入库
                AlarmInfo alarmInfo = new AlarmInfo();
                alarmInfo.setRuleId(rule.getId());
                alarmInfo.setLevel(rule.getLevel());
                alarmInfo.setAlarmContent(produceAlarmInfo.getAlarmContent());
                alarmInfo.setAlarmObject(alarmObject);
                alarmInfo.setRuleType(rule.getRuleType());
                alarmInfo.setIp(produceAlarmInfo.getIp());
                Date createTime = produceAlarmInfo.getCreateTime();
                alarmInfo.setCreateTime(createTime);
                alarmInfo.setMachineHandle(isMachineParticipation ? HANDLE_NO : HANDLE_COMPLETE);
                alarmInfo.setMachineHandleTime(isMachineParticipation ? 0L : createTime.getTime());
                if (alarmInfo.getMachineHandle() == HANDLE_COMPLETE) {
                    redis.del(key);
                    redis.del(alarmUntreatedKey);
                }
                return save(alarmInfo);
            }
        }
        return false;
    }

    @Override
    public boolean updateAlarmInfo(ClearAlarmInfo clearAlarmInfo, AlarmRuleInfo rule) {
        String alarmObject = clearAlarmInfo.getAlarmObject();
        String keySuffix = rule.getId() + ":" + alarmObject;
        redis.del(REDIS_KEY_ALARM_COUNT + keySuffix);

        LambdaQueryWrapper<AlarmInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AlarmInfo::getRuleId, rule.getId())
                .eq(AlarmInfo::getAlarmObject, alarmObject)
                .eq(AlarmInfo::getMachineHandle, HANDLE_NO);
        long result = count(wrapper);
        if (result > 1) {
            // 说明告警不存在需要入库
            AlarmInfo alarmInfo = new AlarmInfo();
            alarmInfo.setMachineHandle(HANDLE_COMPLETE);
            alarmInfo.setMachineHandleTime(clearAlarmInfo.getMachineHandleTime());
            boolean r = update(alarmInfo, wrapper);
            if (r) {
                redis.del(REDIS_KEY_ALARM_UNTREATED + keySuffix);
            }
            return r;
        }
        return false;
    }

    /**
     * 根据规则类型和事件编号查询告警规则信息
     *
     * @param ruleType  规则类型
     * @param eventCode 事件编号
     * @return {@link List}<{@link AlarmRuleInfo}>
     */
    public List<AlarmRuleInfo> findAlarmRuleInfo(String ruleType, String eventCode) {
        return ruleInfoService.findByRuleTypeAndEventCode(ruleType, eventCode);
    }

    @Override
    public void clearAlarmInfoHandle(ClearAlarmInfo clearAlarmInfo) {
        String ruleType = clearAlarmInfo.getRuleType();
        String eventCode = clearAlarmInfo.getEventCode();
        Double threshold = clearAlarmInfo.getThreshold();
        List<AlarmRuleInfo> ruleList = findAlarmRuleInfo(ruleType, eventCode).stream().filter(o -> o.getThreshold() >= threshold).collect(Collectors.toList());
        for (AlarmRuleInfo rule : ruleList) {
            updateAlarmInfo(clearAlarmInfo, rule);
        }
    }

    @Override
    public void produceAlarmInfoHandle(ProduceAlarmInfo produceAlarmInfo) {
        String ruleType = produceAlarmInfo.getRuleType();
        String eventCode = produceAlarmInfo.getEventCode();
        Double threshold = produceAlarmInfo.getThreshold();
        List<AlarmRuleInfo> ruleList = findAlarmRuleInfo(ruleType, eventCode).stream().filter(o -> o.getThreshold() <= threshold).collect(Collectors.toList());
        for (AlarmRuleInfo rule : ruleList) {
            insertAlarmInfo(produceAlarmInfo, rule);
        }
    }

}